﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xunit;
using M3G;

namespace MyTestProject {

    public class TestIntersect {
        #region Field
        Mesh msh;
        #endregion

        public TestIntersect () {
            var stripLengths = new int[] { 4 };
            var indices = new int[] { 0, 1, 2, 3 };

            var ibuf = new IndexBuffer (PrimitiveType.Triangles, stripLengths, indices);
            var vbuf = new VertexBuffer ();

            var positions = new VertexArray (4, 3, ArrayType.Float);
            var normals   = new VertexArray (4, 3, ArrayType.Byte);
            var scale = 1;
            var bias  = new float[] { 0, 0, 0 };
            positions.Set (0, 4, new float[] { 1, -1, 0, 1, 1, 0, -1, -1, 0, -1, 1, 0 });
            normals.Set (0, 4, new byte[] { 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 255 });
            vbuf.SetPositions (positions, scale, bias);
            vbuf.SetNormals (normals);

            // メッシュ
            msh = new Mesh (1, 0);
            msh.SetVertexBuffer (vbuf);
            msh.SetIndexBuffer (0, ibuf);
        }

        /**
         * トライアングル１つとレイの交差判定のテスト
         * */
        [Fact]
        public void TestRayTriangleIntersect () {
            var nod  = new Mesh (1, 0);
            var p1   = new float[] { 0, 0, 0 };
            var p2   = new float[] { 1, 0, 0 };
            var p3   = new float[] { 0.5f, 1, 0 };
            var dir  = new float[] { 0, 0, -1 };
            var pos0 = new float[] { 0.5f, 0.5f, 10 };
            var pos1 = new float[] { 1f, 1f, 10 };
            var pos2 = new float[] { 0f, 1f, 10 };
            var pos3 = new float[] { 0.5f, -0.5f, 10 };
            var ri   = new RayIntersection ();

            // hit
            Assert.Equal (true, nod.RayTriangleIntersection (p1, p2, p3, pos0, dir, ri));
            Assert.Equal (10.0f, ri.Distance, new FloatComparere (0.00001f));

            // miss
            Assert.Equal (false, nod.RayTriangleIntersection (p1, p2, p3, pos1, dir, ri));
            Assert.Equal (false, nod.RayTriangleIntersection (p1, p2, p3, pos2, dir, ri));
            Assert.Equal (false, nod.RayTriangleIntersection (p1, p2, p3, pos3, dir, ri));
        }

        /**
         * メッシュとレイの交差判定テスト
         * もっとも簡単なヒット
         * */
        [Fact]
        public void TestIntersectMeshHit () {

            var pos = new float[] { 0, 0, 10 };
            var dir = new float[] { 0, 0, -1 };
            var ri = new RayIntersection ();
            var expectedNormal   = new float[] { 0, 0, 1 };

            var intersected = msh.Intersect (0xffffffff, pos, dir, ri);

            Assert.Equal (true, intersected);
            Assert.Equal (msh, ri.Intersected);
            Assert.Equal (10f, ri.Distance, new FloatComparere (0.00001f));
            Assert.Equal (expectedNormal, ri.Normal);
        }

        /**
         * メッシュとレイの交差判定テスト
         * もっとも簡単なミスヒット(Z方向)
         * */
        [Fact]
        public void TestIntersectMeshMissZ () {

            var dir = new float[] { 0, 0, -1 };
            var ri = new RayIntersection ();

            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { 2, 0, 10 }, dir, ri));
            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { -2, 0, 10 }, dir, ri));
            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { 0, 2, 10 }, dir, ri));
            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { 0, -2, 10 }, dir, ri));

        }

        /**
         * Meshとレイの交差判定テスト
         * もっとも簡単なミスヒット(Y方向)
         * */
        [Fact]
        public void TestIntersectMeshMissY () {
            var dir = new float[] { 0, -1, 0 };
            var ri  = new RayIntersection ();

            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { 0, 10, 2 }, dir, ri));
            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { 0, 10, -2 }, dir, ri));
            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { 2, 10, 0 }, dir, ri));
            Assert.Equal (false, msh.Intersect (0xffffffff, new float[] { -2, 10, 0 }, dir, ri));
        }

        /**
         * Groupとレイの交差判定テスト
         * 子ノードへ判定処理が回っていることを確認するテスト
         * */
        [Fact]
        public void TestIntersectGroup () {
            var grp = new Group ();
            grp.AddChild (msh);

            var pos = new float[] { 0, 0, 10 };
            var dir = new float[] { 0, 0, -1 };
            var ri  = new RayIntersection ();

            Assert.Equal (true, grp.Intersect (0xffffffff, pos, dir, ri));

            grp.RemoveChild (msh);
        }

        /**
       　* Groupとレイの交差判定テスト
         * 子ノードへ伝播するときにレイの座標変換を正しく行っていることを確認するテスト
         * */
        [Fact]
        public void TestIntersectGroupTransform () {
            var grp1 = new Group ();
            var grp2 = new Group ();
            grp1.AddChild (grp2);
            grp2.AddChild (msh);

            grp2.Translate (10, 0, 0);              // 右に10移動

            var pos1 = new float[] { 0, 0, 10 };    // ヒットしてはいけない
            var pos2 = new float[] { 10, 0, 10 };   // こっちでヒット
            var dir = new float[] { 0, 0, -1 };
            var ri  = new RayIntersection ();

            Assert.Equal (false, grp1.Intersect (0xffffffff, pos1, dir, ri));
            Assert.Equal (true, grp1.Intersect (0xffffffff, pos2, dir, ri));

            grp2.RemoveChild (msh);
        }


        /**
         * Groupとレイの交差判定テスト
         * Groupノードに設定したバウンディングボリュームがある場合Groupノードが代表して交差判定行い
         * 子ノードへ判定処理を回さないことを確認するテスト
         * */
        [Fact]
        public void TestIntersectGroupBoundingVolume () {
            var grp = new Group ();
            grp.AddChild (msh);
            grp.SetBoundingSphere (100, 100, 100, 1);  // このBSphでカリングされる

            var pos = new float[] { 0, 0, 10 };
            var dir = new float[] { 0, 0, -1 };
            var ri  = new RayIntersection ();

            Assert.Equal (false, grp.Intersect (0xffffffff, pos, dir, ri));

            grp.RemoveChild (msh);
        }

        /**
         * スコープの一致しないメッシュを検出しないことを確認するテスト
         * */
        [Fact]
        public void TestIntersectScope () {
            var pos = new float[] { 0, 0, 10 };
            var dir = new float[] { 0, 0, -1 };
            var ri = new RayIntersection ();

            Assert.Equal (true, msh.Intersect (0xffffffff, pos, dir, ri));   // スコープが一致する
            Assert.Equal (false, msh.Intersect (0, pos, dir, ri));          // スコープが一致しない
        }


    }
}
